package jupiter.channel;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.UUID;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import jupiter.message.Message;
import jupiter.servlets.SessionListener;
import jupiter.util.Application;
import jupiter.util.Sync;
import jupiter.util.Timey;
import jupiter.filters.FilterException;
import org.mortbay.util.ajax.Continuation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Session implements ChannelInterface {
	
	/**
	 * Represents a connection, these are the only things not directly added to the DomainChannel
	 * 
	 * 
	 * @param sessionid - the session id to send to, something like hank#jmvc.com@jabbify.com/123456
	 * user#domain@jabbify.com/sessionid
	 * 
	 * setup rules for session
	 */

	Logger logger = LoggerFactory.getLogger(Session.class);
	private ArrayList<String> messages = new ArrayList<String>();
	private Continuation continuation = null;
	private Address address;
	private ArrayList<ChannelInterface> fromChannels = new  ArrayList<ChannelInterface>(); //channels I get messages from
	private Boolean continuation_sync = true;
	protected JSONObject data;
	private Boolean invalidated = false;
	public Session( Message mess) throws FilterException {

		String secret = null;
		try{
			data = mess.data;
			address = new Address( data.getString("address")  ); //can check if get Identity, then no user
		}catch(JSONException e){
			throw new FilterException("{error: 'address not in data'}");
		}
		try{
			secret = data.getString("secret");
			data.remove("secret");
			if(!data.has("publicInfo"))	data.put("publicInfo", new JSONObject());
			JSONObject pi = data.getJSONObject("publicInfo");
			pi.put("createdAt", new java.util.Date().getTime());
			pi.put("referer", mess.request.getHeader("Referer") != null ? mess.request.getHeader("Referer") : JSONObject.NULL); //we might not want this
		}catch(JSONException e){
			throw new FilterException("{error: 'secret not found in data'}");
		}

		Domain dn = Global.getDomain(address);

		Channel user = dn.getOrCreateChannel( address.hashlessAddress() ); //needs to create channel if not there

		if(   user.secret.compareTo(secret.toString()) != 0) throw new FilterException("{error: 'incorrect secret'}");
		
		//see if domain has user id, add this as listener, otherwise create user

		user.addListener(this, true);
		dn.addChannel(this );
		
	}
	public String name(){
		Channel pc = (Channel) parentChannel();
		String name = pc.name();
		return name;
	}
	
	public ChannelInterface parentChannel(){
		return fromChannels.get(0);
	}
	//public boolean 
	/**
	 * Takes and saves messages
	 */
	public JSONObject sendMessage(Message m){
//		Sync.s(this,"sendMessage");
		synchronized (messages) {
			Sync.r(this,"sendMessage");
			messages.add(m.json.toString());
			Sync.u(this,"sendMessage");
		}
		
		
		//resume if we have a continuation
		resume();
		return null;
	}
	public void resume(){
		synchronized (continuation_sync) {
			if(continuation != null)
				continuation.resume();
			continuation = null;
		}
		
	}
	public void addContinuation(Continuation c){
		synchronized (continuation_sync) {
			continuation = c;
		}
	}
	/**
	 * Gets messages. Always called before the continuation is added, if there is one it should be stale.
	 * 
	 * @return
	 * @throws JSONException
	 */
	public String getMessages() throws FilterException {
		String result;
		synchronized (messages) {
			if(messages.size() == 0) return null;
			result = messages.toString();
			messages = new ArrayList<String>();
		}
		return result;
	}
	
	
	public void addListener(ChannelInterface channel, boolean callback) throws FilterException {
		throw new FilterException("{error: 'you cant listen on a session channel'}");
	}
	public void removeListener(ChannelInterface channel, boolean callback) throws FilterException{
		throw new FilterException("{error: 'you cant listen on a session channel'}");
	}
	public void addedAsListener(ChannelInterface channel){
		synchronized(fromChannels){
			fromChannels.add(channel);
		}
		
	}
	public void removedAsListener(ChannelInterface channel) throws FilterException {

		synchronized(fromChannels){
			fromChannels.remove(channel);
		}
		
	}
	public Address address(){
		return address;
	}
	public void invalidate(HttpSession s){
		synchronized(invalidated){
			if(!invalidated){
				s.invalidate();
				invalidated = true;
			}
			
		}
	}
	public void invalidate(){
		synchronized(invalidated){
			if(!invalidated){
				invalidated = true;
			}
			
		}
	}
	public boolean isInvalid(){
		Boolean invalid;
		synchronized(invalidated){
			invalid = invalidated.booleanValue();
		}
		return invalid;
	}
	/**
	 * Could be called twice, but that would be ok, dc.removeChannel should handle this case
	 */
	public void destroy() throws FilterException {
		Domain dc= Global.getDomain(address);
		synchronized(fromChannels){
			
			//remove myself from each fromChannel
			for(ChannelInterface f : fromChannels){
				f.removeListener( this, false);  // basically removes from parent
			}
			fromChannels.clear();
		}
		dc.removeChannel( this);
	}
	public JSONObject map() throws JSONException {
		return new JSONObject();
	}

	public boolean canSendMessagesTo(ChannelInterface channel){
		return false;
	}
	//secret should already be removed
	public JSONObject publicInfo(){
		try{
			data.getJSONObject("publicInfo").put("address", address().toString());
			return data.getJSONObject("publicInfo");
		}catch(JSONException e){
			return new JSONObject();
		}
	}
	public JSONObject update(JSONObject d) throws FilterException{
		data = d;
		return data;
	}
	public JSONObject updatePublicInfo(JSONObject d) throws FilterException{
		try{
			data.put("publicInfo", d);
			return d;
		}catch(JSONException e){
			throw new FilterException("{error: 'unable to update public info'");
		}
	}
	
	public JSONObject modify(JSONObject d) throws FilterException{
		data = d;
		return null;
	}
	public JSONObject toJSON(boolean secret){
		return data;
	}
	public JSONObject subscribeAndUpdate(Address a) throws FilterException {
		throw new FilterException("{error: 'cant subscribe with a session'}");
	}
	public JSONObject unsubscribeAndUpdate(Address a) throws FilterException {
		throw new FilterException("{error: 'cant unsubscribe with a session'}");
	}
	public String toString(){
		return address().toString();
	}
}